from datetime import datetime, timedelta
from typing import Dict, List, Set, Optional, Callable

from numpy import ndarray
from pandas import DataFrame

from vnpy.trader.setting import SETTINGS
from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.object import BarData, TickData, HistoryRequest
from vnpy.trader.utility import round_to, ZoneInfo
from vnpy.trader.datafeed import BaseDatafeed

import copy
import os
import re
import vnpy.tools.utility
from .tdxdata_converter import guess_filename, get_tdx_price

# 观察数据可知, 在通达信里,
# 分时线的(10:24=指数:3872.45)对应一分钟线的(10:25=收盘价:3872.45)
# 一分钟线:
# SSE,000300,2022-12-27 10:19:00,O=3872.26,H=3873.26,L=3872.26,C=3872.69,
# SSE,000300,2022-12-27 10:20:00,O=3872.40,H=3872.99,L=3872.19,C=3872.70,
# SSE,000300,2022-12-27 10:21:00,O=3872.45,H=3873.82,L=3872.45,C=3873.18,
# SSE,000300,2022-12-27 10:22:00,O=3873.07,H=3873.80,L=3873.02,C=3873.46,
# SSE,000300,2022-12-27 10:23:00,O=3873.46,H=3874.21,L=3873.36,C=3873.40,
# SSE,000300,2022-12-27 10:24:00,O=3873.35,H=3873.43,L=3871.90,C=3872.57,
# SSE,000300,2022-12-27 10:25:00,O=3872.50,H=3872.50,L=3871.94,C=3872.45,
# SSE,000300,2022-12-27 10:26:00,O=3872.26,H=3872.45,L=3871.76,C=3872.21,
# 五分钟线:
# SSE,000300,2022-12-27 10:25:00,O=3872.45,H=3874.21,L=3871.90,C=3872.45,
# 观察数据可知, "10:25"是"10:21,10:22,10:23,10:24,10:25"合成的,
# 经过推理可知, 通达信合成的五分钟线, 时间戳取的是截止时间戳,
# 合理推理可知, 通达信一分钟线的时间戳取的是截止时间戳, 即"10:21"=[10:20:00,10:21:00)
# 合理推理可知, 通达信的分时线的时间戳取的是开始时间戳, 即"10:20"=[10:20:00,10:21:00)
# 通达信盘后数据, 一分钟线的时间戳取的是截止时间戳, 应该在进入vnpy时转换成开始时间戳,
INTERVAL_ADJUSTMENT_MAP: Dict[Interval, timedelta] = {
    Interval.MINUTE05: timedelta(minutes=5),
    Interval.MINUTE: timedelta(minutes=1),
    Interval.DAILY: timedelta()         # no need to adjust for daily bar
}


CHINA_TZ = ZoneInfo("Asia/Shanghai")


class TdxdataDatafeed(BaseDatafeed):
    """
    通达信盘后数据(盘后数据下载)TdxData数据服务接口
    可参考 https://gitee.com/vnpy/vnpy_rqdata 升级该代码
    """

    def __init__(self):
        """"""
        self.username: str = SETTINGS["datafeed.username"]
        self.password: str = None  # useless field

        self.basepath: str = self.username  # 通达信盘后数据的根目录, 例如"C:\new_tdx\vipdoc"

        self._000001_mtime: float = 0  # "上证指数"日线文件的最后更新时间
        self._000001_days: List[datetime] = []  # "上证指数"日线文件里的所有交易日

        self.inited: bool = False

    def init(self, output: Callable = print) -> bool:
        """初始化"""
        if self.inited:
            return True

        if not self.username:
            output(r'please set "datafeed.username"(default: C:\new_tdx\vipdoc)')
            return False

        # 通达信金融终端 > 选项 > 盘后数据下载 > 沪深京日线 >
        # 勾选"日线和实时行情数据" > 取消勾选"下载所有AB股类品种的日线数据" > 开始日期选择"1990-01-01" >
        # 添加品种 > 所有沪深京指数 > 疑似在第17列中下部位有两个"上证指数" > 选择这两个"上证指数" > 确定 > 开始下载
        output('请先通过"通达信金融终端"下载"000001.SSE"的完整的日线(1990-12-19~NOW), 用于正确标识夜盘数据的"自然日期时间"!')

        self.inited = True
        return True

    def init_000001_days(self, output: Callable = print) -> None:
        """
        以"上证指数"日线文件里的所有交易日为准
        """
        filepath: Optional[str] = guess_filename(
            exchange=Exchange.SSE.value,
            symbol="000001",
            interval=Interval.DAILY.value,
            basepath=self.basepath,
            output=output,
        )

        assert isinstance(filepath, str)

        _000001_mtime: float = os.path.getmtime(filename=filepath)
        if self._000001_mtime == _000001_mtime:
            return

        df: DataFrame = get_tdx_price(
            exchange=Exchange.SSE.value,
            symbol="000001",
            interval=Interval.DAILY.value,
            start=datetime(year=1990, month=12, day=19),
            end=datetime.now(),
            start_business_day=None,
            end_business_day=None,
            basepath=self.basepath,
            days=self._000001_days,
            output=output,
        )
        self._000001_days: List[datetime] = df.index.to_pydatetime().tolist()

        self._000001_mtime = _000001_mtime

    def query_bar_history(self, req: HistoryRequest, output: Callable = print) -> Optional[List[BarData]]:
        """查询K线数据"""
        if False:
            pass
        else:
            return self._query_bar_history(req, output)

    def _query_bar_history(self, req: HistoryRequest, output: Callable = print) -> Optional[List[BarData]]:
        """查询K线数据"""
        req = copy.deepcopy(req)
        req.symbol = self.conv_symbol_4_tdx(exchange=req.exchange, symbol=req.symbol)
        req.__post_init__()

        if req.interval == Interval.SECOND05:
            # "五秒线"请查看 vnpy_zxtools/tdx_detail_to_bar.py 脚本,
            return []

        if not self.inited:
            n: bool = self.init(output=output)
            if not n:
                return []

        self.init_000001_days(output=output)

        symbol: str = req.symbol
        exchange: Exchange = req.exchange
        interval: Interval = req.interval
        start: datetime = req.start
        end: datetime = req.end

        # 为了将通达信时间戳（K线结束时点）转换为VeighNa时间戳（K线开始时点）
        adjustment: timedelta = INTERVAL_ADJUSTMENT_MAP[interval]

        # 为了查询夜盘数据（因为[周五的23:59触发查询, 需求能查询到周一的23:59的数据]所以要加3）
        # 因为已经将通达信文件里的"交易日内的时间戳"转换成了"自然时刻的时间戳", 所以已经不需要修改end的值了,
        # end += timedelta(days=3)

        df: Optional[DataFrame] = get_tdx_price(
            exchange=exchange.value,
            symbol=symbol,
            interval=interval.value,
            start=start,
            end=end,
            start_business_day=None,
            end_business_day=None,
            basepath=self.basepath,
            days=self._000001_days,
            output=output,
        )

        data: List[BarData] = []

        if df is not None:
            # 填充NaN为0
            df.fillna(0, inplace=True)

            for row in df.itertuples():
                # TODO: 学习 row.Index[0] 的用法,
                # dt: datetime = row.Index[1].to_pydatetime() - adjustment
                dt: datetime = row.Index.to_pydatetime() - adjustment
                dt: datetime = dt.replace(tzinfo=CHINA_TZ)

                business_day: datetime = row.business_day.to_pydatetime().replace(tzinfo=CHINA_TZ)

                # 以 BaseData/BarData 定义字段的顺序为准, 方便直接对比类的定义,
                bar: BarData = BarData(
                    gateway_name="TDX",
                    symbol=symbol,
                    exchange=exchange,
                    datetime=dt,
                    interval=interval,
                    volume=row.volume,
                    turnover=row.turnover,
                    open_interest=getattr(row, "open_interest", 0),
                    open_price=round_to(row.open, 0.000001),
                    high_price=round_to(row.high, 0.000001),
                    low_price=round_to(row.low, 0.000001),
                    close_price=round_to(row.close, 0.000001),
                )

                bar.symbol = self.conv_symbol_4_use(exchange=bar.exchange, symbol=bar.symbol)
                bar.__post_init__()

                data.append(bar)

        return data

    def query_tick_history(self, req: HistoryRequest, output: Callable = print) -> Optional[List[TickData]]:
        """查询Tick数据"""
        return []  # 无法查询

    @classmethod
    def conv_symbol_4_tdx(cls, exchange: Exchange, symbol: str) -> str:
        """要查询tdx的数据, 需要做相应的转换"""
        symbol: str = symbol.upper()
        symbol: str = cls.czce_3_to_4(exchange=exchange, symbol=symbol)
        return symbol

    @classmethod
    def conv_symbol_4_use(cls, exchange: Exchange, symbol: str) -> str:
        """转换成能直接使用的格式"""
        symbol: str = symbol.upper()
        symbol: str = cls.czce_4_to_3(exchange=exchange, symbol=symbol)
        symbol: str = cls.dce_gfex_ine_shfe_u_to_l(exchange=exchange, symbol=symbol)
        return symbol

    @classmethod
    def czce_3_to_4(cls, exchange: Exchange, symbol: str) -> str:
        """
        暂时处理"期货"和"期权", 数据格式如下所示:
        'InstrumentID': 'MA409', 'InstrumentName': '甲醇9月',
        'InstrumentID': 'MA409C2500', 'InstrumentName': '甲醇409买权',
        暂不处理'跨期套利/跨品种套利', 数据格式如下所示:
        'InstrumentID': 'SPD SF501&SF503', 'InstrumentName': '跨期套利-SF501/SF503',
        'InstrumentID': 'IPS SF501&SM501', 'InstrumentName': '跨品种套利-SF501/SM5',
        """
        if exchange == Exchange.CZCE:
            reMatch: re.Match = re.match(pattern="^(?P<PRODUCT>[a-zA-Z]+)(?P<DATE>[0-9]{3})(?P<OPTION>([cpCP][0-9]+)?)$", string=symbol)
            if reMatch is not None:
                mapping: dict = reMatch.groupdict()
                INT1: int = datetime.now().year % 100 // 10  # 一位int值  (# TODO: 暂时维持到2028年左右仍能正常使用)
                symbol: str = f'{mapping["PRODUCT"].upper()}{INT1}{mapping["DATE"]}{mapping["OPTION"]}'
        return symbol

    @classmethod
    def czce_4_to_3(cls, exchange: Exchange, symbol: str) -> str:
        """"""
        if exchange == Exchange.CZCE:
            reMatch: re.Match = re.match(pattern="^(?P<PRODUCT>[a-zA-Z]+)(?P<DATE>[0-9]{4})(?P<OPTION>([cpCP][0-9]+)?)$", string=symbol)
            if reMatch is not None:
                mapping: dict = reMatch.groupdict()
                INT3: int = int(mapping["DATE"]) % 1000  # 三位int值
                symbol: str = f'{mapping["PRODUCT"].upper()}{INT3}{mapping["OPTION"]}'
        return symbol

    @classmethod
    def dce_gfex_ine_shfe_u_to_l(cls, exchange: Exchange, symbol: str) -> str:
        """upper_to_lower"""
        if exchange in (Exchange.DCE, Exchange.GFEX, Exchange.INE, Exchange.SHFE):
            product: str = vnpy.tools.utility.get_product(symbol=symbol)
            assert product == symbol[:len(product)]
            symbol: str = product.lower() + symbol[len(product):]
        return symbol

    def query_bar_history_inner(
        self,
        symbol: str,
        exchange: Exchange,
        start_business_day: datetime,
        end_business_day: datetime = None,
        interval: Interval = None,
        output: Callable = print,
    ) -> Optional[List[BarData]]:
        """
        查询K线数据, 未被使用, 忘记为啥写它了, 暂时保留它,
        """
        if not self.inited:
            n: bool = self.init(output=output)
            if not n:
                return []

        self.init_000001_days(output=output)

        # 为了将通达信时间戳（K线结束时点）转换为VeighNa时间戳（K线开始时点）
        adjustment: timedelta = INTERVAL_ADJUSTMENT_MAP[interval]

        df: Optional[DataFrame] = get_tdx_price(
            exchange=exchange.value,
            symbol=symbol,
            interval=interval.value,
            start=None,
            end=None,
            start_business_day=start_business_day,
            end_business_day=end_business_day,
            basepath=self.basepath,
            days=self._000001_days,
            output=output,
        )

        data: List[BarData] = []

        if df is not None:
            # 填充NaN为0
            df.fillna(0, inplace=True)

            for row in df.itertuples():
                # TODO: 学习 row.Index[0] 的用法,
                # dt: datetime = row.Index[0].to_pydatetime() - adjustment
                dt: datetime = row.Index.to_pydatetime() - adjustment
                dt: datetime = dt.replace(tzinfo=CHINA_TZ)

                business_day: datetime = row.business_day.to_pydatetime()

                bar: BarData = BarData(
                    gateway_name="TDX",
                    symbol=symbol,
                    exchange=exchange,
                    datetime=dt,
                    interval=interval,
                    volume=row.volume,
                    turnover=row.turnover,
                    open_interest=getattr(row, "open_interest", 0),
                    open_price=round_to(row.open, 0.000001),
                    high_price=round_to(row.high, 0.000001),
                    low_price=round_to(row.low, 0.000001),
                    close_price=round_to(row.close, 0.000001),
                )

                data.append(bar)

        return data


if __name__ == "__main__":
    pass
